from __future__ import print_function ## a hack to enable print as a function. This MUST come as the first line.
import json
import sys
import logging
import puza_row as pr
import puza_table as pt

class PuzaMemoryTable(pt.PuzaTable):
    def __init__(self):
        pt.PuzaTable.__init__(self)
        self._MEOF = False
        self._buffer_pointer = -1

    def ReadLines(self, func = lambda x:print(x)):
        self.Load()
        for i in range(len(self._local_buffer)):
            func(self._local_buffer[i])
            
    def InputFile(self, filename =''):        
        pt.PuzaTable.InputFile(self, filename)
    
    def CheckLoad(self):
        if len(self._local_buffer) == 0:
            self.Load()        
        
    def Load(self, ignore = 0):
        self._local_buffer = []
        line_no = 1
        flag = 0
        while True:
            raw_obj = self.StreamInRow()            
            if raw_obj != None:
                #try:
                r = pr.PuzaRow.Load(raw_obj)
                self._local_buffer.append(r)
                #except:
                #    if ignore == 0:
                #        print('Line '+str(line_no) + ' error.')
                #        print(str(raw_obj))
                #        flag = 1
                #        break
                line_no = line_no + 1
            else:
                break
        if flag == 1:
            sys.exit()
    
    def Save(self, lformat = 0, block_number = 1):
        l = len(self._local_buffer)
        for i in range(len(l)):
            self.StreamInRow(self._local_buffer[i], lformat = lformat, block_number = block_number)
        self.StreamClose()

    @staticmethod
    def Verify(filename = ''):
        A = PuzaMemoryTable()
        A.InputFile(filename)        
        error = 1
        try:
            A.Load()
        except:
            error = 0
        return error
    
    def Length(self):
        logging.info('Start Length')
        self.CheckLoad()
        return len(self._local_buffer)
    
    def OutputFile(self, filename = ''):
        pt.PuzaTable.OutputFile(self, filename)     
    
    def GetKeys(self,key):
        key_buffer = set()
        for i in range(len(self.__buffer)):
            key_buffer.add(self.__buffer[i][key])
        return key_buffer
    
    def SetKey(self,key):
        self.__key = key

    ## perform 'A - B' by key
    def __sub__(self, B):
        ## prepare all records
        key = self.__key
        B_keys = B.GetKeys(key)
        ## do difference
        objs = PuzaMemoryTable()
        for i in range(len(self.__buffer)):
            current_key = self.__buffer[i][key]
            if not current_key in B_keys:
                objs.Append(self.__buffer[i])
        return objs
    
    ## access PuzaMemoryTable[k]
    def __getitem__(self, k):
        return self._local_buffer[k]
    
    ## set PuzaMemoryTable[k] = v
    def __setitem__(self, k, v):
        self._local_buffer[k] = v
    
    def __len__(self):
        return self.Length()
    
    ## append
    def Append(self, obj):
        ROW = None
        if type(obj) is dict:
            ROW = pr.PuzaRow(obj)
        elif type(obj) is str:
            obj = json.loads(obj)
            ROW = pr.PuzaRow(obj)
        elif isinstance(obj, pr.PuzaRow):
            ROW = obj
        self._local_buffer.append(ROW)
        
    
    def Dump(self):
        return self.__str__()
    
    def __str__(self):
        local_buffer = []
        for i in range(len(self._local_buffer)):
            local_buffer.append(self._local_buffer[i].__str__())
        return '\n'.join(local_buffer)
    
    def __merge_structure(self, a):
        a_set = set(a)
        for item_name in a_set:
            if item_name in self._inner_structure:
                self._inner_structure[item_name]['counts'] = self._inner_structure[item_name]['counts'] + 1
            else:
                self._inner_structure[item_name]= a[item_name]
                self._inner_structure[item_name]['counts'] = 1
                self._inner_structure[item_name]['level'] = a[item_name]['level']
        
    def GetStructure(self):
        logging.info('Start to obtain the table structure.')
        self.CheckLoad()
        self._inner_structure = {}
        for i in range(len(self._local_buffer)):
            self.__merge_structure(self._local_buffer[i].GetStructure())
        logging.info('Finish to obtain the table structure.')      
        return self._inner_structure
    
    def Rows(self, start_n, end_n):
        self.CheckLoad()
        if start_n < 0:
            start_n = 0
        if start_n >= len(self._local_buffer):
            start_n = len(self._local_buffer) - 1
        if end_n < 0:
            end_n = len(self._local_buffer) - 1 ## set to the whole data
        if end_n >= len(self._local_buffer):
            end_n = len(self._local_buffer) - 1
        rows_buffer = self._local_buffer[start_n:end_n]
        return rows_buffer            
